#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>


#define MAXSIZE 128
#define MAXARGS 32

//命令行参数表
char* gargv[MAXARGS];
int gargc = 0;
const char* gsep = " ";

//最近一个命令执行完毕的退出码
int lastcode = 0;


//自己的工作路径
char cwd[MAXSIZE];

//环境变量表
char* genv[MAXARGS];
int genvc = 0;

void LogEnv()
{
    //正常情况，环境变量表内部是从配置文件来的
    //今天我们从父进程拷贝
    extern char **environ;
    for(; environ[genvc]; genvc++)
    {
        genv[genvc] = (char*)malloc(sizeof(char)* 4096);
        strcpy(genv[genvc], environ[genvc]);
    }
    genv[genvc] = NULL;
    printf("Loat env:\n");
    for(int i = 0; i < genvc; i++)
        printf("genv[%d]: %s\n", i, genv[i]);
}

static std::string rfindDir(const std::string &p)
{
    if(p == "/") return p;

    const std::string psep = "/";
    auto pos = p.rfind(psep);
    if(pos == std::string::npos)
        return std::string();

    return p.substr(pos+1);
}

const char* GetUserName()
{
    char* username = getenv("USER");
    if(username == NULL) return (char*)"None";
    
    return username;
}
const char* GetPwd()
{

    char* pwd = getenv("PWD");
    //char* pwd = getcwd(cwd, sizeof(cwd));
    if(pwd == NULL) return (char*)"None";
    return pwd;
}

const char* GetHostName()
{

    char* hostname = getenv("HOSTNAME");
    if(hostname == NULL) return (char*)"None";
    
    return hostname;
}
void PrintCommandLine()
{
    printf("[%s@%s %s]#",GetUserName(), GetHostName(), rfindDir(GetPwd()).c_str());
    fflush(stdout);
}

int GetCommand(char commandline[], int size)
{

    if(fgets(commandline, size, stdin) == NULL) 
        return 0;
    commandline[strlen(commandline)-1] = '\0';

    return strlen(commandline);
}
int ParseCommand(char commandline[])
{
    gargc = 0;
    memset(gargv, 0, sizeof(gargv));
    gargv[0] = strtok(commandline, gsep);
    while((gargv[++gargc] = strtok(NULL, gsep)));

  //  printf("gargc: %d\n", gargc);
  //  int i = 0;
  //  for(; gargv[i]; i++)
  //      printf("gargv[%d]:%s\n", i, gargv[i]);
    return 0;
}
// 0 不是内建命令
// 1 是内建命令&&执行完毕 
int CheckBuiltinExecute()
{
    if(strcmp(gargv[0], "cd") == 0)
    {
        //内建命令
        if(gargc == 2)
        {
            //1.更改进程内核中的路径
            chdir(gargv[1]);
            //2.更改环境变量
             
            char pwd[1024];
            getcwd(pwd, sizeof(pwd));
            snprintf(cwd, sizeof(cwd), "PWD=%s", pwd);
            putenv(cwd);
            lastcode = 0;
        }
        return 1;
    }
    else if(strcmp(gargv[0], "echo") == 0)
    {
        if(gargc == 2)
        {
            if(gargv[1][0] == '$')
            {
                if(strcmp(gargv[1]+1, "?") == 0)
                {
                    printf("lastcode: %d\n", lastcode);
                }
                else if(strcmp(gargv[1]+1, "PATH") == 0)
                {
                    printf("%s\n", getenv("PATH"));//putenv 和getenv的区别
                }

                lastcode = 0;
            }
        }
        return 1;
    }
    return 0;
}
int ExecuteCommand()
{
    pid_t id = fork();

    if(id < 0) 
        return -1;
    else if(id == 0)
    {
        //child
        execvpe(gargv[0], gargv, genv);
        exit(1);
    }
    else
    {
        //father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            //printf("wait child success!\n");
            lastcode = WEXITSTATUS(status);
        }
    }
    return 0;
}
int main()
{
    // 0.从配置文件中获取环境变量 填充环境变量表
    LogEnv();
    
    char command_line[MAXSIZE] = {0};
    while(1)
    {
        //1.打印命令行字符串
        PrintCommandLine();
        //2.获取用户输入
        if(0 == GetCommand(command_line, sizeof(command_line)))
            continue;
        
        //printf("%s\n", command_line);
        
        //3.解析字符串 "ls -a -l" -> "ls" "-a" "-l"
        ParseCommand(command_line);    
        //4.执行命令,判断子进程执行还是父进程bash(内建命令)执行
        if(CheckBuiltinExecute()) // > 0
        {
            continue;
        }

        //5.让子进程执行这个命令
        ExecuteCommand();
    }


    return 0;
}
